﻿using System;
using System.Collections.Generic;
using System.Threading;
using MugenInjection;
using MugenInjection.Interface;
using MugenInjection.Scope;
using MugenInjection.Syntax.Constant;
using MugenInjection.Syntax.Type;
using MugenMvvmToolkit.Interfaces;
using MugenMvvmToolkit.Ioc;
using MugenMvvmToolkit.Ioc.Parameters;
using MugenMvvmToolkit.Utils;
using ConstructorParameter = MugenInjection.Parameters.ConstructorParameter;
using PropertyParameter = MugenInjection.Parameters.PropertyParameter;

// ReSharper disable CheckNamespace

namespace MugenMvvmToolkit
// ReSharper restore CheckNamespace
{
    /// <summary>
    ///     Represents the MugenInjection ioc adapter.
    /// </summary>
    public class MugenIocAdapter : IIocAdapter
    {
        #region Fields

        private static int _idCounter;
        private readonly IInjector _injector;

        #endregion

        #region Constructor

        /// <summary>
        ///     Initializes a new instance of the <see cref="MugenIocAdapter" /> class.
        /// </summary>
        public MugenIocAdapter(IInjector injector, IIocAdapter parent = null)
        {
            Should.NotBeNull(injector, "injector");
            _injector = injector;
            Container = _injector;
            Parent = parent;
            Id = Interlocked.Increment(ref _idCounter);
        }

        #endregion

        #region Implementation of IDisposable

        /// <summary>
        ///     Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <filterpriority>2</filterpriority>
        public void Dispose()
        {
            _injector.Dispose();
        }

        #endregion

        #region Methods

        /// <summary>
        ///     Converts parameters.
        /// </summary>
        private static IInjectionParameter[] ConvertParameters(IList<IIocParameter> parameters)
        {
            if (parameters == null || parameters.Count == 0) return new IInjectionParameter[0];
            var result = new List<IInjectionParameter>();
            foreach (IIocParameter iocParameterBase in parameters)
            {
                switch (iocParameterBase.ParameterType)
                {
                    case ParameterType.Constructor:
                        result.Add(new ConstructorParameter(iocParameterBase.Name, iocParameterBase.Value));
                        break;
                    case ParameterType.Method:
                        result.Add(
                            new MugenInjection.Parameters.MethodParameter(
                                ((MethodParameter) iocParameterBase).MethodName, iocParameterBase.Name,
                                iocParameterBase.Value));
                        break;
                    case ParameterType.Property:
                        result.Add(new PropertyParameter(iocParameterBase.Name, iocParameterBase.Value));
                        break;                    
                }
            }
            return result.ToArray();
        }

        private static IScopeLifecycle GetScope(DependencyLifecycle dependencyLifecycle)
        {
            switch (dependencyLifecycle)
            {
                case DependencyLifecycle.SingleInstance:
                    return new SingletonScopeLifecycle();
                case DependencyLifecycle.TransientScopeInstance:
                    return new TransientScopeLifecycle();
                default:
                    Thrower.EnumOutOfRange("dependencyLifecycle", dependencyLifecycle);
                    return null;
            }
        }

        #endregion

        #region Implementation of IIocAdapter

        /// <summary>
        ///     Gets the id of <see cref="IIocAdapter" />.
        /// </summary>
        public int Id { get; private set; }

        /// <summary>
        ///     Gets a value indicating whether this instance is disposed.
        /// </summary>
        public bool IsDisposed
        {
            get { return _injector.IsDisposed; }
        }

        /// <summary>
        ///     Gets the parent ioc adapter.
        /// </summary>
        public IIocAdapter Parent { get; private set; }

        /// <summary>
        ///     Gets the original ioc container.
        /// </summary>
        public object Container { get; private set; }

        /// <summary>
        ///     Creates a child ioc adapter.
        /// </summary>
        /// <returns>
        ///     An instance of <see cref="IIocAdapter" />.
        /// </returns>
        public IIocAdapter CreateChild()
        {
            this.NotBeDisposed();
            return new MugenIocAdapter(_injector.CreateChild(), this);
        }

        /// <summary>
        ///     Gets an instance of the specified service.
        /// </summary>
        /// <param name="service">The specified service type.</param>
        /// <param name="name">The specified binding name.</param>
        /// <param name="parameters">The specified parameters.</param>
        /// <returns>An instance of the service.</returns>
        public object Get(Type service, string name = null, params IIocParameter[] parameters)
        {
            this.NotBeDisposed();
            Should.NotBeNull(service, "service");
            if (name == null)
                return _injector.Get(service, ConvertParameters(parameters));
            return _injector.Get(service, name, null, ConvertParameters(parameters));
        }

        /// <summary>
        ///     Gets all instances of the specified service.
        /// </summary>
        /// <param name="service">Specified service type.</param>
        /// <param name="name">The specified binding name.</param>
        /// <param name="parameters">The specified parameters.</param>
        /// <returns>An instance of the service.</returns>
        public IEnumerable<object> GetAll(Type service, string name = null, params IIocParameter[] parameters)
        {
            this.NotBeDisposed();
            Should.NotBeNull(service, "service");
            if (name == null)
                return _injector.GetAll(service, ConvertParameters(parameters));
            return _injector.GetAll(service, name, null, ConvertParameters(parameters));
        }

        /// <summary>
        ///     Indicates that the service should be bound to the specified constant value.
        /// </summary>
        public void BindToConstant(Type service, object constValue, string name = null)
        {
            this.NotBeDisposed();
            Should.NotBeNull(service, "service");
            IConstantObjectPriorityWhenSyntax syntax = _injector
                .BindWithManualBuild(service)
                .ToConstant(constValue);
            if (name != null)
                syntax.NamedBinding(name);
            syntax.Build();
        }

        /// <summary>
        ///     Indicates that the service should be bound to the specified type.
        /// </summary>
        /// <param name="service">The specified service type.</param>
        /// <param name="typeTo">The specified to type</param>
        /// <param name="name">The specified binding name.</param>
        /// <param name="dependencyLifecycle">
        ///     The specified <see cref="DependencyLifecycle" />
        /// </param>
        public void Bind(Type service, Type typeTo, DependencyLifecycle dependencyLifecycle, string name = null)
        {
            this.NotBeDisposed();
            Should.NotBeNull(service, "service");
            Should.NotBeNull(typeTo, "typeTo");
            ITypeCallbackConstructorObjectPriorityUseWithSyntax syntax = _injector
                .BindWithManualBuild(service)
                .To(typeTo)
                .InScope(GetScope(dependencyLifecycle));
            if (name != null)
                syntax.NamedBinding(name);
            syntax.Build();
        }

        /// <summary>
        ///     Unregisters all bindings with specified conditions for the specified service.
        /// </summary>
        /// <param name="service">The specified service type.</param>
        public void Unbind(Type service)
        {
            this.NotBeDisposed();
            Should.NotBeNull(service, "service");
            _injector.Unbind(service);
        }

        /// <summary>
        ///     Determines whether the specified request can be resolved.
        /// </summary>
        /// <param name="service">The specified service type.</param>
        /// <returns>
        ///     <c>True</c> if the specified service has been resolved; otherwise, <c>false</c>.
        /// </returns>
        public bool CanResolve(Type service)
        {
            this.NotBeDisposed();
            Should.NotBeNull(service, "service");
            return _injector.CanResolve(service, true, false);
        }

        #endregion

        #region Implementation of IServiceProvider

        /// <summary>
        ///     Gets the service object of the specified type.
        /// </summary>
        /// <returns>
        ///     A service object of type <paramref name="serviceType" />.
        ///     -or-
        ///     null if there is no service object of type <paramref name="serviceType" />.
        /// </returns>
        /// <param name="serviceType">
        ///     An object that specifies the type of service object to get.
        /// </param>
        object IServiceProvider.GetService(Type serviceType)
        {
            return _injector.GetService(serviceType);
        }

        #endregion
    }
}